package it.uniba.di.mas.jpnde.behaviour;

import java.io.IOException;

import it.uniba.di.mas.jpnde.core.PlaceEvent;
import it.uniba.di.mas.jpnde.core.ITokenSenderListener;
import it.uniba.di.mas.jpnde.core.Token;
import jade.core.AID;
import jade.core.behaviours.Behaviour;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.UnreadableException;
import jade.util.leap.ArrayList;

public class PNEventBehaviour extends Behaviour
	implements ITokenSenderListener
	{

	private final static short AWAIT_EVENT=0;
	private final static short INIT_EVENT=1;
	private final static short DO_EVENT=2;
	private final static short END=3;
	private final static short ERROR=4;
	
	private Thread t=null;
	private TokenSender sender=null;
	
	int state=AWAIT_EVENT;
	private PlaceEvent event=null;
	private String errorMessage="";
	private long tokenReceived=0;
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	@Override
	public void action() {
		// TODO Auto-generated method stub
		switch(state){
		case AWAIT_EVENT:
			receiveEvent();
			break;
		case INIT_EVENT:
			initEvent();
			break;
		case DO_EVENT:
			doEvent();
			break;
		}	
	}

	@Override
	public boolean done() {
		// TODO Auto-generated method stub
		return state==ERROR||state==END;
	}
	
	private void receiveEvent(){
		System.out.println("PNEventBehaviour.receiveEvent ");
		ACLMessage message=myAgent.receive();
		try
			{
			if (message!=null)
				{
				ACLMessage reply=message.createReply();
				if ((message.getPerformative()!=ACLMessage.REQUEST)||
				    !(message.getContentObject() instanceof PlaceEvent))
					{
						state=ERROR;
						reply.setPerformative(ACLMessage.FAILURE);
					}	
				else
					{
						event=(PlaceEvent)message.getContentObject();
						state=INIT_EVENT;
						reply.setPerformative(ACLMessage.INFORM);
					}
				myAgent.send(reply);
				}
			else
				block();
			}
		catch(Exception e)
			{e.printStackTrace();}
	
	}
	
	private void initEvent(){
		System.out.println("PNEventBehaviour.initEvent :"+event.getId());
		if (!event.isOutType()){
				sender=new TokenSender(event,this);
				t=new Thread(sender);
				t.start();
			}
		state=DO_EVENT;
	}
	
	private void doEvent(){
		System.out.println("PNEventBehaviour.doEvent :"+event.getId());
		ACLMessage message=myAgent.receive();
		if (message!=null)
			{
			if ((message.getSender().getName().equals(event.getManagerId()))&&
			   	(message.getPerformative()==ACLMessage.CANCEL)){
					state=END;
					return;
				}
			
			if (event.isOutType())
				{
				if ((message.getSender().getName().equals(event.getManagerId()))&&
					(message.getPerformative()==ACLMessage.INFORM)){
					tokenReceived++;
					System.out.println("PNEventBehaviour.doEvent :"+event.getId()+" tokens >> "+tokenReceived);
					if (!event.getRoute().equals("")&&
						 message.hasByteSequenceContent())
						{
						try {
							Token token=(Token)message.getContentObject();
							token.setEvent(event);
							ACLMessage resend=new ACLMessage(ACLMessage.INFORM);
							resend.addReceiver(new AID(event.getRoute(),AID.ISGUID));
							resend.setContentObject(token);
							myAgent.send(resend);
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
							errorMessage="PNEventBehaviour.doEvent :"+event.getId()+":"+e.getMessage();
							state=ERROR;
							}
						}
					return;
					}
				}
			else
				{
				// input type
				try {
					if (message.hasByteSequenceContent()&&
					    message.getPerformative()==ACLMessage.INFORM&&
					    message.getContentObject() instanceof Token){
							Token token=(Token)message.getContentObject();
							token.setEvent(event);
							sender.addToken(token);
							return;
					}
				} catch (UnreadableException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					errorMessage="PNEventBehaviour.doEvent :"+event.getId()+":"+e.getMessage();
					state=ERROR;
					return;
					}
				}
		    System.out.println("PNManagerRun.doEvent : message not recognized >> "+message);
			}
		else
			block();
	}
	
	public int onEnd() {
		System.out.println("PNEventBehaviour.onEnd :"+event.getId());
		// Gestire l'errore in maniera unificata sull' onEnd
		int ret=super.onEnd();
		
		if (sender!=null){
			sender.stop();
			if (t!=null&&t.isAlive())
				t.interrupt();
		}
		
		if ((state==ERROR)&&(!errorMessage.equals("")))
			{
			ACLMessage message=new ACLMessage(ACLMessage.FAILURE);
			message.addReceiver(new AID(event.getManagerId(),AID.ISGUID));
			message.setContent(errorMessage);
			myAgent.send(message);
			}
		myAgent.doDelete();
		return ret;
	}
	
	public class TokenSender implements Runnable
	{
		private PlaceEvent event=null;
		private ITokenSenderListener listener=null;
		
		private ArrayList tokenList=new ArrayList();
		private boolean stop=false;
		
		public TokenSender(PlaceEvent event,ITokenSenderListener listener){
			this.event=event;
			this.listener=listener;
			}
		
		public void stop(){
			stop=true;
		}
		
		public synchronized void addToken(Token token)
		{
			tokenList.add(token);
			System.out.println("TokenSender.addToken(): "+event.getId()+" tokens >>> "+tokenList.size());
			notifyAll();
		}
		
		public synchronized Token getToken()
			throws InterruptedException
		{
			while (tokenList.size()==0)
				wait();
			Token token=(Token)tokenList.get(0);
			tokenList.remove(0);
			return token;
		}
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(true){
				if (stop) break;	
				if (event.isAuto())
					{
					// Generazione automatica token
					try {
						Thread.sleep(event.getDelay());
						} catch (InterruptedException e) {
						// TODO Auto-generated catch block
							e.printStackTrace();
							listener.errorAction("TokenSender.run():"+event.getId()+"InterruptedException :"+
									e.getMessage());
							return;
						}
					
					// Invio messaggio con Token
					ACLMessage message=new ACLMessage(ACLMessage.INFORM);
					message.addReceiver(new AID(event.getManagerId(),AID.ISGUID));
					try {
						message.setContentObject(new Token(event.getId(),event));
						} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						listener.errorAction("TokenSender.run() :"+event.getId()+":"+
								e.getMessage());
						return;
						}
					myAgent.send(message);
					}
				else
					{
					// not is auto
					try {
						Token token=this.getToken();
						Thread.sleep(event.getDelay());
						
						ACLMessage message=new ACLMessage(ACLMessage.INFORM);
						message.addReceiver(new AID(event.getManagerId(),AID.ISGUID));
						try {
							message.setContentObject(token);
							} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
							listener.errorAction("TokenSender.run() :"+event.getId()+":"+
									e.getMessage());
							return;
							}
						myAgent.send(message);
					}
					catch (Exception e){
						e.printStackTrace();
						listener.errorAction("TokenSender.run() :"+event.getId()+":"+
								e.getMessage());
						return;
						}
					}
			}	
		}
	}

	@Override
	public void errorAction(String errorMessage) {
		// TODO Auto-generated method stub
		System.out.println("PNManagerRun.errorAction :"+event.getId());
		this.errorMessage=errorMessage;
		state=ERROR;
	}
		
}
